/**
 * Copyright 2020-9999 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.heima.ThreadPool.DIYThreadPool;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/*
 * @description 手动实现线程池！！！
 * @params
 * @return
 */

public class ThreadPoolExecutor implements Executor {
    /*
     * @description workQueue——任务队列，里面存储的是一个一个的要执行的任务，定义的是一个以Runnable为泛型的阻塞队列BlockingQueue
     */

    private BlockingQueue<Runnable> workQueue;

    /*
     * @description 因为我们的shutdown()方法里面需要遍历每一个线程调用他们的中断线程方法，所以我们需要一个集合存储每一个启动任务的工作线程
     *
     */
    private List<WorkThread> workThreads = new ArrayList<>();

    private static final int DEFAULT_QUEUE_SIZE = 5;

    /*
     * @description 为了简单只定义了标准7参数的两个方法
     * poolSize就是线程的数量(未区分最大线程、核心线程)
     *
     */
    public ThreadPoolExecutor(int poolSize, BlockingQueue<Runnable> workQueue){
        this.workQueue = workQueue;
        /*
         * @description 这里最核心，直接为每一个线程创建了工作线程对象，再在里面启动工作线程
         */
        for(int i = 0; i < poolSize; i++){
            WorkThread workThread = new WorkThread();
            workThread.start();
            //这里将每一个工作线程对象放入集合中，方便后续遍历执行终止操作
            workThreads.add(workThread);
        }
    }

    /*
     * @description 为了简便，单参数的Constructor默认线程数量大小为5
     */
    public ThreadPoolExecutor(int poolSize){
        this(poolSize, new LinkedBlockingQueue<>(DEFAULT_QUEUE_SIZE));
    }

    /*
     * @description 这里是线程池提交任务的入口，可以看到execute()方法的核心在于将单个线程任务task放入工作阻塞队列中
     */
    public void execute(Runnable task) {
        try {
            workQueue.put(task);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /*
     * @description 我们的线程池关闭方法shutdown()里面需要遍历池子里面的每一个线程的中断方法
     */
    public void shutdown() {
        if (workThreads.size() > 0){
            for (int i = 0; i < workThreads.size(); i++){
                WorkThread workThread = workThreads.get(i);
                workThread.interrupt();
            }
        }
    }

    /**
     * 工作线程————线程池里面的内部类。真正执行任务的类。
     *
     * 首先要实现WorkThread这个工作线程，这是线程池里面真正执行线程的模块。
     * 1.workThread继承了Thread线程原生类，并重写了里面的run()方法.
     * 2.这里需要注意的是workThread这个类需要在shutdown()里面退出。所以说，我们在run()方法里面逻辑是这样的：
     *      2.1首先获取当前线程对象
     *      2.2然后再while(true)循环里面不断尝试从工作队列里面取相应的任务，如果工作队列里面没有任务的话，就会阻塞在这一行代码
     *          Runnable workTask=workQueue.take();
     *          如果说我们执行了execute()方法向线程池里面提交线程了后，这一行代码就会被唤醒，生成可执行的工作任务(Runnable)，之后再调用run()
     *          方法执行相应的任务。
     *      2.3如果说当前线程被中断了，那么就直接捕获这个异常并退出while()
     */
    class WorkThread extends Thread{
        @Override
        public void run() {
            Thread currentThread = Thread.currentThread();
            while (true){
               try{
                   //判断当前线程是否被中断
                   if (currentThread.isInterrupted()){
                       break;
                   }
                   Runnable workTask = workQueue.take();//阻塞队列的方法take()，作用是取任务
                   workTask.run();
                   
                   /*Interpret()方法的是是非非————高频考点！！！

                    * @description 如果我们调用了当前线程的Interpret()方法后，他不会直接中断线程，而是设置线程的"中断状态标志位"，如果线程处理阻塞状态(wait()\sleep()),
                    * 这些方法会抛出一个InterpretException异常。并清除线程的中断标记位。线程便不能正确退出。
                    * 雅退出线程的方式：在线程的运行过程中定期检查 Thread.currentThread().isInterrupted() 来判断是否应该终止线程。
                    * 在捕获 InterruptedException 后，通过再次调用 Thread.currentThread().interrupt() 来确保线程的中断状态得以保留，
                    * 从而可以在适当的地方正确处理中断，最终实现优雅退出。
                    *
                    * 如果我们没有在catch代码块里面再次调用当前线程的Interpret()方法的话，线程的线程中断标记位会被清除后就不在复原了！，
                    * 所以这是完完全全不可以去除的！！
                    */

               }catch (InterruptedException e){
                    currentThread.interrupt();
               }
            }
        }
    }
}
